package cc.shacocloud.mirage.restful.bind.support;

import cc.shacocloud.mirage.restful.HandleMethodArgumentResolver;
import cc.shacocloud.mirage.restful.HttpRequest;
import cc.shacocloud.mirage.restful.bind.ValueConstants;
import cc.shacocloud.mirage.restful.bind.WebDataBinder;
import cc.shacocloud.mirage.restful.bind.WebDataBinderFactory;
import cc.shacocloud.mirage.restful.exception.HttpRequestBindMissingException;
import cc.shacocloud.mirage.restful.exception.HttpRequestBindingException;
import cc.shacocloud.mirage.restful.exception.MethodArgumentTypeMismatchException;
import cc.shacocloud.mirage.utils.MethodParameter;
import cc.shacocloud.mirage.utils.converter.TypeDescriptor;
import cc.shacocloud.mirage.utils.converter.TypeMismatchException;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.vertx.core.Future;
import lombok.Getter;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 抽象基类，用于从指定值解析方法参数。请求参数、请求头和路径变量都是命名值的例子。每个都可能有一个名称、一个是否必需的标志和一个默认值。
 *
 * <p>
 * 子类定义如何做以下事情:
 * <ul>
 *   <li>获取方法参数的命名值信息
 *   <li>将名称解析为参数值
 *   <li>当需要参数值时处理缺少的参数值
 *   <li>可选地处理解析值
 * </ul>
 */
public abstract class AbstractNamedValueMethodArgumentResolver extends AbstractArgumentValidateResolver implements HandleMethodArgumentResolver {
    
    private final Map<MethodParameter, NamedValueInfo> namedValueInfoCache = new ConcurrentHashMap<>(256);
    
    @Override
    public Future<Object> resolveArgument(@NotNull HttpRequest request,
                                          @NotNull MethodParameter parameter,
                                          @Nullable WebDataBinderFactory binderFactory) {
        final NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
        final MethodParameter nestedParameter = parameter.nestedIfOptional();
        
        final String resolvedName = resolveStringValue(namedValueInfo.getName());
        if (resolvedName == null) {
            throw new IllegalArgumentException("指定的名称不能解析为 null: [" + namedValueInfo.getName() + "]");
        }
        
        return resolveName(resolvedName, nestedParameter, request)
                .compose(arg -> {
                    if (arg == null) {
                        // 默认值
                        if (namedValueInfo.getDefaultValue() != null) {
                            arg = resolveStringValue(namedValueInfo.getDefaultValue());
                            // 缺失值的处理情况
                        } else if (namedValueInfo.isRequired() && !nestedParameter.isOptional()) {
                            handleMissingValue(namedValueInfo.getName(), nestedParameter, request);
                        }
                        // 空值的处理情况
                        arg = handleNullValue(namedValueInfo.getName(), arg, nestedParameter.getNestedParameterType());
                    } else if ("".equals(arg) && namedValueInfo.getDefaultValue() != null) {
                        arg = resolveStringValue(namedValueInfo.getDefaultValue());
                    }
                    
                    // 参数转换
                    WebDataBinder binder = null;
                    if (binderFactory != null) {
                        binder = binderFactory.createBinder(request, null, namedValueInfo.getName());
                        try {
                            arg = binder.convertIfNecessary(arg, new TypeDescriptor(parameter));
                        } catch (TypeMismatchException ex) {
                            throw new MethodArgumentTypeMismatchException(arg, ex.getRequiredType(),
                                    namedValueInfo.getName(), parameter, ex);
                        }
                        
                        binder.setTarget(arg);
                    }
                    
                    return handleResolvedValue(arg, namedValueInfo.getName(), binder, parameter, request);
                });
        
    }
    
    /**
     * 获取给定方法参数的命名值。
     */
    private @NotNull NamedValueInfo getNamedValueInfo(MethodParameter parameter) {
        NamedValueInfo namedValueInfo = this.namedValueInfoCache.get(parameter);
        if (namedValueInfo == null) {
            namedValueInfo = createNamedValueInfo(parameter);
            namedValueInfo = updateNamedValueInfo(parameter, namedValueInfo);
            this.namedValueInfoCache.put(parameter, namedValueInfo);
        }
        return namedValueInfo;
    }
    
    /**
     * 为给定的方法参数创建{@link NamedValueInfo}对象。
     * 实现通常通过{@link MethodParameter#getParameterAnnotation(Class)}检索方法注解。
     *
     * @param parameter 该方法的参数
     * @return 命名值信息
     */
    protected abstract NamedValueInfo createNamedValueInfo(MethodParameter parameter);
    
    /**
     * 基于给定的NamedValueInfo创建一个新的NamedValueInfo。
     */
    @Contract("_, _ -> new")
    private @NotNull NamedValueInfo updateNamedValueInfo(MethodParameter parameter, @NotNull NamedValueInfo info) {
        String name = info.getName();
        if (info.getName().isEmpty()) {
            name = parameter.getParameterName();
            if (name == null) {
                throw new IllegalArgumentException("参数类型名称 [" + parameter.getNestedParameterType().getName() + "] 不可用，并且在类文件中也找不到参数名称信息。");
            }
        }
        String defaultValue = (ValueConstants.DEFAULT_NONE.equals(info.defaultValue) ? null : info.defaultValue);
        return new NamedValueInfo(name, info.required, defaultValue);
    }
    
    /**
     * 解析给定的注解指定值，拓展方法。
     */
    @Nullable
    protected String resolveStringValue(String value) {
        return value;
    }
    
    /**
     * 将给定的参数类型和值名称解析为参数值。
     *
     * @param name      要解析的值的名称
     * @param parameter 要解析为参数值的方法参数  (预先嵌套的情况下{@link java.util.Optional})
     * @param request   当前请求
     * @return 已解析的参数
     */
    protected abstract Future<Object> resolveName(String name, MethodParameter parameter, HttpRequest request);
    
    /**
     * 当需要指定值时调用， 但是{@link #resolveName} 返回了{@code null}，并且没有默认值。在这种情况下，子类通常会抛出异常。
     *
     * @param name      值的名称
     * @param parameter 该方法的参数
     * @param request   当前请求
     */
    protected void handleMissingValue(String name, MethodParameter parameter, @NotNull HttpRequest request) throws HttpRequestBindingException {
        // 默认状态码
        request.response().setStatusCode(HttpResponseStatus.BAD_REQUEST.code());
        handleMissingValue(name, parameter);
    }
    
    /**
     * 当需要指定值时调用， 但是{@link #resolveName} 返回 {@code null}，并且没有默认值。在这种情况下，子类通常会抛出异常。
     *
     * @param name      值的名称
     * @param parameter 该方法的参数
     */
    protected void handleMissingValue(String name, @NotNull MethodParameter parameter) throws HttpRequestBindMissingException {
        throw new HttpRequestBindMissingException("无法匹配参数类型为[" + parameter.getNestedParameterType().getSimpleName()
                + "]参数名称为 '" + name + "' 的值");
    }
    
    /**
     * {@code null}会导致{@code Boolean}的{@code false}值，或其他原语的异常。
     */
    @Nullable
    private Object handleNullValue(String name, @Nullable Object value, Class<?> paramType) {
        if (value == null) {
            if (Boolean.TYPE.equals(paramType)) {
                return Boolean.FALSE;
            } else if (paramType.isPrimitive()) {
                throw new IllegalStateException("可选的 " + paramType.getSimpleName() + " 参数 '" + name + "' 存在，但由于声明为原始类型，无法转换为空值。考虑将其声明为对应原语类型的对象包装器。");
            }
        }
        return value;
    }
    
    /**
     * 表示关于命名值的信息，包括名称、是否需要以及默认值。
     */
    @Getter
    protected static class NamedValueInfo {
        
        private final String name;
        
        private final boolean required;
        
        @Nullable
        private final String defaultValue;
        
        public NamedValueInfo(String name, boolean required, @Nullable String defaultValue) {
            this.name = name;
            this.required = required;
            this.defaultValue = defaultValue;
        }
    }
    
}
